package com.haoxuer.discover.plug.data.service.impl;

import com.haoxuer.discover.plug.api.StoragePlugin;
import com.haoxuer.discover.plug.data.service.PluginService;
import com.haoxuer.discover.plug.data.service.StorageService;
import com.haoxuer.discover.plug.data.vo.FileInfo;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Resource;
import javax.servlet.ServletContext;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.multipart.MultipartFile;


@Service("storageServiceImpl")
public class StorageServiceImpl implements StorageService, ServletContextAware {
  /**
   * servletContext
   */
  private ServletContext servletContext;

  @Resource(name = "pluginServiceImpl")
  private PluginService pluginService;

  @Override
  public void setServletContext(ServletContext servletContext) {
    this.servletContext = servletContext;
  }

  /**
   * 添加上传任务
   *
   * @param storagePlugin 存储插件
   * @param path          上传路径
   * @param tempFile      临时文件
   * @param contentType   文件类型
   */
  private void addTask(final StoragePlugin storagePlugin, final String path, final File tempFile, final String contentType) {
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        try {
          storagePlugin.upload(path, tempFile, contentType);
        } finally {
          FileUtils.deleteQuietly(tempFile);
        }
      }
    };
    new Thread(runnable).start();
  }

  @Override
  public boolean isValid(FileInfo.FileType fileType, MultipartFile multipartFile) {
    return false;
  }

  @Override
  public String upload(FileInfo.FileType fileType, MultipartFile multipartFile, boolean async) {
    if (multipartFile == null) {
      return null;
    }
    String uploadPath = getPath(fileType);
    try {
      Map<String, Object> model = new HashMap<String, Object>();
      model.put("uuid", UUID.randomUUID().toString());
      String path = process(uploadPath, model);
      String destPath = path + UUID.randomUUID() + "." + FilenameUtils.getExtension(multipartFile.getOriginalFilename());

      for (StoragePlugin storagePlugin : pluginService.getStoragePlugins(true)) {
        File tempFile = new File(System.getProperty("java.io.tmpdir") + "/upload_" + UUID.randomUUID() + ".tmp");
        if (!tempFile.getParentFile().exists()) {
          tempFile.getParentFile().mkdirs();
        }
        multipartFile.transferTo(tempFile);
        if (async) {
          addTask(storagePlugin, destPath, tempFile, multipartFile.getContentType());
        } else {
          try {
            storagePlugin.upload(destPath, tempFile, multipartFile.getContentType());
          } finally {
            FileUtils.deleteQuietly(tempFile);
          }
        }
        return storagePlugin.getUrl(destPath);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  public String getPath(FileInfo.FileType fileType) {
    String uploadPath;
    if (fileType == FileInfo.FileType.flash) {
      uploadPath = "/upload/flash/${.now?string('yyyyMM')}/";
    } else if (fileType == FileInfo.FileType.media) {
      uploadPath = "/upload/media/${.now?string('yyyyMM')}/";
    } else if (fileType == FileInfo.FileType.file) {
      uploadPath = "/upload/file/${.now?string('yyyyMM')}/";
    } else {
      uploadPath = "/upload/image/${.now?string('yyyyMM')}/";
    }
    return uploadPath;
  }

  @Override
  public String upload(FileInfo.FileType fileType, MultipartFile multipartFile) {
    return upload(fileType, multipartFile, false);
  }

  @Override
  public String uploadLocal(FileInfo.FileType fileType, MultipartFile multipartFile) {
    if (multipartFile == null) {
      return null;
    }
    String uploadPath = getPath(fileType);
    try {
      Map<String, Object> model = new HashMap<String, Object>();
      model.put("uuid", UUID.randomUUID().toString());
      String path = process(uploadPath, model);
      String destPath = path + UUID.randomUUID() + "." + FilenameUtils.getExtension(multipartFile.getOriginalFilename());
      File destFile = new File(servletContext.getRealPath(destPath));
      if (!destFile.getParentFile().exists()) {
        destFile.getParentFile().mkdirs();
      }
      multipartFile.transferTo(destFile);
      return destPath;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  private String process(String uploadPath, Map<String, Object> model) {
    if (uploadPath == null) {
      return null;
    }
    Configuration configuration = new Configuration();
    StringWriter out = new StringWriter();
    try {
      new Template("template", new StringReader(uploadPath), configuration).process(model, out);
    } catch (TemplateException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return out.toString();
  }

  @Override
  public List<FileInfo> browser(String path, FileInfo.FileType fileType, FileInfo.OrderType orderType) {
    if (path != null) {
      if (!path.startsWith("/")) {
        path = "/" + path;
      }
      if (!path.endsWith("/")) {
        path += "/";
      }
    } else {
      path = "/";
    }
    String uploadPath = getPath(fileType);
    String browsePath = StringUtils.substringBefore(uploadPath, "${");
    browsePath = StringUtils.substringBeforeLast(browsePath, "/") + path;

    List<FileInfo> fileInfos = new ArrayList<FileInfo>();
    if (browsePath.indexOf("..") >= 0) {
      return fileInfos;
    }
    for (StoragePlugin storagePlugin : pluginService.getStoragePlugins(true)) {
      fileInfos = storagePlugin.browser(browsePath);
      break;
    }
    if (orderType == FileInfo.OrderType.size) {
      Collections.sort(fileInfos, new SizeComparator());
    } else if (orderType == FileInfo.OrderType.type) {
      Collections.sort(fileInfos, new TypeComparator());
    } else {
      Collections.sort(fileInfos, new NameComparator());
    }
    return fileInfos;
  }

  private class NameComparator implements Comparator<FileInfo> {

    @Override
    public int compare(FileInfo fileInfos1, FileInfo fileInfos2) {
      return new CompareToBuilder().append(!fileInfos1.getIsDirectory(), !fileInfos2.getIsDirectory()).append(fileInfos1.getName(), fileInfos2.getName()).toComparison();
    }
  }

  private class SizeComparator implements Comparator<FileInfo> {
    @Override
    public int compare(FileInfo fileInfos1, FileInfo fileInfos2) {
      return new CompareToBuilder().append(!fileInfos1.getIsDirectory(), !fileInfos2.getIsDirectory()).append(fileInfos1.getSize(), fileInfos2.getSize()).toComparison();
    }
  }

  private class TypeComparator implements Comparator<FileInfo> {

    @Override
    public int compare(FileInfo fileInfos1, FileInfo fileInfos2) {
      return new CompareToBuilder().append(!fileInfos1.getIsDirectory(), !fileInfos2.getIsDirectory()).append(FilenameUtils.getExtension(fileInfos1.getName()), FilenameUtils.getExtension(fileInfos2.getName())).toComparison();
    }
  }
}
